// Package query prepares all needed DB statements during initiliazation and makes them available
// through the Get function.
package query

import (
    "bytes"
    "config"
    "database/sql"
    _ "github.com/bmizerany/pq"
    "log"
    "os"
    "os/exec"
    "text/template"
)

// utility function for checking unmanagable errors.
func chk(e error) {
    if e != nil {
        log.Panic(e)
    }
}

var dbh *sql.DB
var prepared map[string]map[string]*sql.Stmt

func boardExists(boardName string) bool {
    log.Printf("Checking existance of %s", boardName)
    query := `SELECT COUNT(tablename) FROM pg_tables WHERE tablename = $1;`
    var hit int
    dbh.QueryRow(query, boardName).Scan(&hit)
    log.Printf("Query for %s returned %d", boardName, hit)
    if hit > 0 {
        return true
    }
    return false
}

// Process schema template, invoke psql and send it on stdin.
// to have Postgres create the boards.
func addBoard(boardName string) {
    log.Printf("Adding board %s", boardName)
    t, e := template.ParseFiles("query/schema.sql")
    if e != nil {
        log.Panic(e)
    }

    log.Println("Processing template")
    buf := new(bytes.Buffer)
    e = t.Execute(buf, boardName)
    if e != nil {
        log.Panic(e)
    }

    db := config.Defaults.DbName
    cmd := exec.Command("psql", db)
    psqlPipe, _ := cmd.StdinPipe()

    cmd.Start()
    buf.WriteTo(psqlPipe)
    psqlPipe.Close()
    cmd.Wait()
}

func Get(boardName, key string) *sql.Stmt {
    if _, ok := prepared[boardName]; !ok {
        log.Panic("No query map for ", boardName)
    }
    if _, ok := prepared[boardName][key]; !ok {
        log.Panic("No query named " + key + " for " + boardName)
    }
    return prepared[boardName][key]
}

// Prepare all SQL queries at program startup.
func init() {
    cfg := config.Defaults
    os.Setenv("PGHOST", cfg.Pghost) // pq.go will choke with cryptic errors if PGHOST is not set.
    h, err := sql.Open("postgres", "dbname="+cfg.DbName+" sslmode=disable")
    chk(err)
    dbh = h

    log.Println("Opened DB handle")
    prepared = map[string]map[string]*sql.Stmt{}
    for _, b := range config.Boards {
        name := b.TableName
        if !boardExists(name) {
            addBoard(name)
        }

        queries := map[string]string{

            "insertThread": `
INSERT INTO ` + name + `_threads (op, sticky, locked, thread_deleted, last_updated, ghost_updated, all_posts, image_posts, thread_url)
VALUES ($1, $2, $3, $4, $5, $5, 0, 0, $6)`,

            "insertPost": `
INSERT INTO ` + name + ` (post_id, ghost_id, time, name, trip, subject, mail, poster_uid, body, spoiler, parent, image, img_name, img_url, ip, passwd, highlight, auth)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)`,

            "insertImg": `
INSERT INTO images (md5, size, full_x, full_y, thumb_x, thumb_y)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING int_id`,

            "updateImgWithImgFile": `
UPDATE images SET img_file = $1 WHERE int_id = $2`,

            "updateImgWithThumbFile": `
UPDATE images SET thumb_file = $1 WHERE int_id = $2`,

            "updateExpiredThread": `
UPDATE ` + name + `_threads SET expired = now(), thread_deleted = $2 WHERE op = $1`,

            "updateDeletedPost": `
UPDATE ` + name + ` SET post_deleted = now() WHERE post_id = $1 AND time = $2`,

            "updatePostBody": `
UPDATE ` + name + ` SET body = $1 WHERE post_id = $2 AND time = $3`,

            "getImageByMd5": `
SELECT int_id FROM images WHERE md5 = $1`,
        }

        prep := map[string]*sql.Stmt{}
        for k, v := range queries {
            log.Printf("Preparing SQL statement %s for %s", k, name)
            stmt, err := dbh.Prepare(v)
            chk(err)
            prep[k] = stmt
        }
        log.Printf("Adding query map for %s", name)
        prepared[name] = prep
    }
}
